import numpy as np
import math


# 定义激活函数
def sigmoid(x):
    return 1.0 / (1.0 + math.exp(-x))


def sigmoid_d(x):
    return x * (1.0 - x)


class NN:
    # 初始化
    def __init__(self, num_in, num_hide, num_out):
        # 接收输入层、隐藏层、输出层神经元个数
        self.num_in = num_in
        self.num_hide = num_hide
        self.num_out = num_out

        # 根据神经元个数生成各层神经元输入值矩阵，初始均为0.0
        self.neurons_in = [0.0] * self.num_in
        self.neurons_hide = [0.0] * self.num_hide
        self.neurons_out = [0.0] * self.num_out

        # 初始化参数
        # self.w_in = np.random.rand(self.num_in, self.num_hide).tolist()
        # self.w_hide = np.random.rand(self.num_hide, self.num_out).tolist()
        self.w_in = np.random.normal(1, 0.2, (self.num_in, self.num_hide)).tolist()
        self.w_hide = np.random.normal(1, 0.2, (self.num_hide, self.num_out)).tolist()
        self.d_hide = [0.1] * self.num_hide
        self.d_out = [0.1] * self.num_out

    # 前向传播
    def fp(self, features):
        # 取得输入层神经元值
        for i in range(self.num_in):
            self.neurons_in[i] = features[i]

        # 计算隐藏层神经元值
        for j in range(self.num_hide):
            sum = 0.0
            for i in range(self.num_in):
                sum += self.neurons_in[i] * self.w_in[i][j]
            self.neurons_hide[j] = sigmoid(sum - self.d_hide[j])

        # 计算输出层神经元值
        for k in range(self.num_out):
            sum = 0.0
            for j in range(self.num_hide):
                sum += self.neurons_hide[j] * self.w_hide[j][k]
            self.neurons_out[k] = sigmoid(sum - self.d_out[k])

        return self.neurons_out

    # 反向传播
    def bp(self, labels, lr):
        # 计算输出层神经元梯度项
        deviation_out = [0.0] * self.num_out  # 用于保存输出层神经元梯度项
        error = 0  # 用于保存代价函数
        for k in range(self.num_out):
            deviation = labels[k] - self.neurons_out[k]
            deviation_out[k] = deviation * sigmoid_d(self.neurons_out[k])
            error += 0.5 * (deviation ** 2)  # 计算代价函数

        # 计算隐藏层神经元梯度项
        deviation_hide = [0.0] * self.num_hide  # 用于保存隐藏层神经元梯度项
        for j in range(self.num_hide):
            deviation = 0.0
            for k in range(self.num_out):
                deviation += deviation_out[k] * self.w_hide[j][k]
            deviation_hide[j] = deviation * sigmoid_d(self.neurons_hide[j])

        # 更新隐藏层权值和输出层阈值
        for j in range(num_hide):
            for k in range(num_out):
                self.w_hide[j][k] += lr * deviation_out[k] * self.neurons_hide[j]
                self.d_out[k] -= lr * deviation_out[k]

        # 更新输入层权值和隐藏层阈值
        for i in range(self.num_in):
            for j in range(self.num_hide):
                self.w_in[i][j] += lr * deviation_hide[j] * self.neurons_in[i]
                self.d_hide[j] -= lr * deviation_hide[j]

        return error


# 读入数据集
train_path = "./Iris/Iris-train.txt"
test_path = "./Iris/Iris-test.txt"

features_train = []
labels_train = []
features_test = []
labels_test = []


# 转换为独热码
def one_hot(labels):
    for i in range(len(labels)):
        if labels[i] == 0:
            labels[i] = [1, 0, 0]
        elif labels[i] == 1:
            labels[i] = [0, 1, 0]
        else:
            labels[i] = [0, 0, 1]
    return labels


with open(train_path, 'r') as f_train:
    for line in f_train:
        line = line.split()
        features_train.append(list(map(float, line[:4])))
        labels_train.append(list(map(int, line[-1]))[0])
    labels_train = one_hot(labels_train)

with open(test_path, 'r') as f_test:
    for line in f_test:
        line = line.split()
        features_test.append(list(map(float, line[:4])))
        labels_test.append(list(map(int, line[-1]))[0])

accu_list = []  # 存储每次的准确率
time = 10  # 运行次数

for time in range(time):

    print('time:', time + 1)
    # 初始化一个神经网络
    num_in = 4
    num_hide = 10
    num_out = 3
    nn = NN(num_in, num_hide, num_out)

    # 训练神经网络
    epoch = 10000  # 迭代10000次
    for epoch in range(epoch):
        for i in range(len(features_train)):
            nn.fp(features_train[i])
            error = nn.bp(labels_train[i], lr=0.05)
        if (epoch + 1) % 1000 == 0:
            print('epoch:', epoch + 1, 'error:', error)

    # 测试训练效果
    count = 0
    for j in range(len(features_test)):
        result = nn.fp(features_test[j])
        category = result.index(max(result))
        count += (category == labels_test[j])
    accuracy = float(count / len(features_test))
    print('accu:', accuracy)
    print('--------------------')
    accu_list.append(accuracy)

print('accu_list:', accu_list)
print('average:', np.mean(accu_list), 'variance', np.var(accu_list))
